
import os
from utils import checksum
from files.mocks import *
import uuid
import shutil
# // PackagePool is deduplicated storage of package files on filesystem
class PackagePool :
    # sync.Mutex

    rootPath=''
    supportLegacyPaths=None

    # // LegacyPath returns path relative to pool's root for pre-1.1 aptly (based on MD5)
    def LegacyPath(pool,filename , checksums ) :
        filename = os.path.basename(filename)
        if filename == "." or filename == "/" :
            print("filename %s is invalid", filename)
            return ""
        hashMD5 = checksums.MD5

        if len(hashMD5) < 4 :
            print("unable to compute pool location for filename %v, MD5 is missing", filename)
            return ""
        return os.path.join(hashMD5[0:2], hashMD5[2:4], filename)


    # // buildPoolPath generates pool path based on file checksum
    def buildPoolPath(pool,filename , checksums ) :
        filename = os.path.basename(filename)
        if filename == "." or filename == "/" :
            print("filename %s is invalid", filename)
            return ""
        

        hash = checksums.SHA256

        if len(hash) < 4 :
            # // this should never happen in real life
            print("unable to compute pool location for filename %v, SHA256 is missing", filename)
            return ""
        return os.path.join(hash[0:2], hash[2:4], hash[4:32]+"_"+filename)

    # // Remove deletes file in package pool returns its size
    def Remove(pool ,path :str) ->int:
        # pool.Lock()
        # defer pool.Unlock()

        path = os.path.join(pool.rootPath, path)

        info = os.path.getsize(path)

        os.remove(path)
        return info

    def ensureChecksums(pool,poolPath, fullPoolPath , checksumStorage):
        targetChecksums= checksumStorage.Get(poolPath)

        if targetChecksums is None :
            # // we don't have checksums stored yet for this file
            targetChecksums = checksum.ChecksumInfo()
            targetChecksums = checksum.ChecksumsForFile(fullPoolPath)
            checksumStorage.Update(poolPath, targetChecksums)
        return
    def Verify(pool,poolPath, basename:str , checksums:checksum.ChecksumInfo , checksumStorage ) :
        possiblePoolPaths = []

        if poolPath != "" :
            possiblePoolPaths = possiblePoolPaths.append( poolPath)
        else :
            # // try to guess
            if checksums.SHA256 != "" :
                modernPath= pool.buildPoolPath(basename, checksums)
                possiblePoolPaths = possiblePoolPaths.append( modernPath)
            

            if pool.supportLegacyPaths and checksums.MD5 != "" :
                legacyPath = pool.LegacyPath(basename, checksums)

                possiblePoolPaths = possiblePoolPaths.append( legacyPath)
            
        

        for path in possiblePoolPaths :
            fullPoolPath = os.path.join(pool.rootPath, path)

            if os.path.getsize(fullPoolPath) != checksums.Size :
                # // oops, wrong file?
                continue
            

            targetChecksums = pool.ensureChecksums(path, fullPoolPath, checksumStorage)


            if checksums.MD5 != "" and targetChecksums.MD5 != checksums.MD5 or checksums.SHA256 != "" and targetChecksums.SHA256 != checksums.SHA256 :
                # // wrong file?
                return ""

            # // fill back checksums
            checksums = targetChecksums
            return path
        

        return ""
    # // Open returns io.ReadCloser to access the file
    def Open(pool,path ) :
        return os.path.join(pool.rootPath, path)


    # // Stat returns Unix stat(2) info
    def Stat(pool,path ) :
        return os.stat(os.path.join(pool.rootPath, path))

    # // Link generates hardlink to destination path
    def Link(pool,path, dstPath :str):
        return os.link(os.path.join(pool.rootPath, path), dstPath)


    # // Symlink generates symlink to destination path
    def Symlink(pool ,path, dstPath :str):
        return os.symlink(os.path.join(pool.rootPath, path), dstPath)


    # // FullPath generates full path to the file in pool
    # //
    # // Please use with care: it's not supposed to be used to access files
    def FullPath(pool,path ):
        return os.path.join(pool.rootPath, path)


    # // GenerateTempPath generates temporary path for download (which is fast to import into package pool later on)
    def GenerateTempPath(pool,filename ) :
        random = str(uuid.uuid4())
        return os.path.join(pool.rootPath, random[0:2], random[2:4], random[4:]+filename)
    def Import(pool,srcPath, basename :str, checksums :checksum.ChecksumInfo, move :bool, checksumStorage) ->str:
        # pool.Lock()
        # defer pool.Unlock()

        import errno
        sourceSize= os.path.getsize(srcPath)

        if checksums.MD5 == "" or checksums.SHA256 == "" or checksums.Size != sourceSize :
            # // need to update checksums, MD5 and SHA256 should be always defined
            checksums = checksum.ChecksumsForFile(srcPath)
            

        # // build target path
        poolPath = pool.buildPoolPath(basename, checksums)

        fullPoolPath = os.path.join(pool.rootPath, poolPath)
        isNotError=False
        targetSize=None
        try:
            targetSize = os.path.getsize(fullPoolPath)
        except OSError as e:
            if e.errno==errno.ENOENT:
                isNotError=True
        # if True:
            # // target already exists and same size
        if not isNotError:
            if targetSize == sourceSize :
                

                targetChecksums:checksum.ChecksumInfo = pool.ensureChecksums(poolPath, fullPoolPath, checksumStorage)


                checksums = targetChecksums
                return poolPath
            raise Exception("unable to import into pool: file {} already exists".format(fullPoolPath))

        # // trying to overwrite file?
        # raise("unable to import into pool: file %s already exists",fullPoolPath)
        
# 这里的报错药看配置文件，只有改成true才可以，但是貌似有问题
        if pool.supportLegacyPaths :
            # // file doesn't exist at new location, check legacy location

            legacyPath = pool.LegacyPath(basename, checksums)
            
            legacyFullPath = os.path.join(pool.rootPath, legacyPath)
            
            legacyTargetSize = os.path.getsize(legacyFullPath)
            # if True :
                # // legacy file exists
            if legacyTargetSize == sourceSize :
                # // file exists at legacy path and it's same size, consider it's already in the pool
                

                targetChecksums:checksum.ChecksumInfo = pool.ensureChecksums(legacyPath, legacyFullPath, checksumStorage)

                checksums = targetChecksums
                return legacyPath
                

                # // size is different, import at new path
            
        

        # // create subdirs as necessary
        poolDir = os.path.dirname(fullPoolPath)
        try:
            err = os.makedirs(poolDir, mode=0o777)
        except Exception as e:
            print('---Error:',e)
            pass

        # // check if we can use hardlinks instead of copying/moving

    # 判断是否同一个文件系统，如果是尝试使用link
        err=None
        if getmount(poolDir)==getmount(srcPath):
            # // same filesystem, try to use hardlink
            err = os.link(srcPath, fullPoolPath)
        else:
            err = True
        

        if err:
            shutil.copyfile(srcPath,fullPoolPath)

        if not err:
            if not checksums.Complete() :
                # // need full checksums here
                checksums = checksum.ChecksumsForFile(srcPath)
                
                

            checksumStorage.Update(poolPath, checksums)
            err=False
        

        if err and move :
            os.remove(srcPath)
        

        return poolPath


    # // FilepathList returns file paths of all the files in the pool
    def FilepathList(pool,progress  ):# ([]string, error) {
        pool.Lock()
        # defer pool.Unlock()

        dirs = os.listdir(pool.rootPath)
        # if err is not None  {
        #     if os.IsNotExist(err) {
        #         return None, None
        #     }
        #     return None, err
        # }

        if len(dirs) == 0 :
            return None, None
        

        # if progress is not None  {
        #     progress.InitBar(int64(len(dirs)), false, aptly.BarGeneralBuildFileList)
        #     defer progress.ShutdownBar()
        # }

        result = []
        # def walkNoName(path :str ):
            
        for dir in dirs :
            for root, dirs, files in os.walk(os.path.join(pool.rootPath, dir.Name()), topdown=True):
                for i in files:
                    path=os.path.join(root,i)
                    result.append(path[len(pool.rootPath)+1:])
                    
        

        return result, None
    



# // Check interface
# var (
# 	_ aptly.PackagePool      = (*PackagePool)(None)
# 	_ aptly.LocalPackagePool = (*PackagePool)(None)
# )

# // NewPackagePool creates new instance of PackagePool which specified root
def NewPackagePool(root:str, supportLegacyPaths :bool):

    rootPath = os.path.join(root, "pool")

    rootPath= os.path.abspath(rootPath)

    packagepool=PackagePool()
    # DONE 大概是这里有问题
    packagepool.rootPath=rootPath
    packagepool.supportLegacyPaths=supportLegacyPaths
    return packagepool




# // Verify checks whether file exists in the pool and fills back checksum info
# //
# // if poolPath is empty, poolPath is generated automatically based on checksum info (if available)
# // in any case, if function returns true, it also fills back checksums with complete information about the file in the pool

# // Import copies file into package pool
# //
# // - srcPath is full path to source file as it is now
# // - basename is desired human-readable name (canonical filename)
# // - checksums are used to calculate file placement
# // - move indicates whether srcPath can be removed


def getmount(path):        
    path = os.path.realpath(os.path.abspath(path))
    while path != os.path.sep:
        if os.path.ismount(path):
            return path
        path = os.path.abspath(os.path.join(path, os.pardir))
    return path
